/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// OpenGL ES 2.0 code

#include <jni.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/nonfree/nonfree.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/video/tracking.hpp>
#include <android/log.h>

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>


#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define  LOG_TAG    "libgl2jni"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO ,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

static void printGLString(const char *name, GLenum s) 
{
    const char *v = (const char *) glGetString(s);
    LOGI("GL %s = %s\n", name, v);
}

static void checkGlError(const char* op) 
{
    for (GLint error = glGetError(); error; error = glGetError()) 
    {
        LOGI("after %s() glError (0x%x)\n", op, error);
    }
}



static const char gVertexShader[] = 
    "attribute vec4 vPosition;		                            			\n"
	"attribute vec2 vUV;			                           				\n"	
    "void main() {					                            			\n"
    "  gl_Position = vPosition;		                            			\n"	
    "}								                            			\n";



static const char gFragmentShader[] = 
    "precision mediump float;                                   			\n"
	"in vec2 UV;															\n"
	"uniform sampler2D myTextureSampler;									\n"
	 "out vec3 color;														\n"
    "void main() {															\n"
    " gl_FragColor = texture2D( myTextureSampler, UV ).rgba;				\n"	 
    "}                                                          			\n";



static const char strVShader[] =
    "attribute vec4 vPosition;												\n" 
    "attribute vec2 vUV;													\n" 
    "varying vec2 v_texCoords;												\n" 
    "void main()															\n" 
    "{																		\n" 
          "gl_Position = vPosition;											\n" 
          "v_texCoords = vUV;												\n" 
    "}																		\n";



static const char strFShader[] =
    "precision mediump float;												\n" 
    "varying vec2 v_texCoords;												\n" 
    "uniform sampler2D myTextureSampler;									\n" 
    "void main()															\n" 
    "{																		\n" 
      "gl_FragColor = texture2D(myTextureSampler, v_texCoords);				\n" 
    "}																		\n";



static const char backgroundVertexProgram[] =
    "#version 330 core														\n"
    "																		\n"
    "// Input vertex data, different for all executions of this shader.		\n"
    "layout(location = 0) in vec3 vertexPosition_modelspace;				\n"
    "layout(location = 1) in vec2 vertexUV;									\n"
    "																		\n"
    "// Output data ; will be interpolated for each fragment.				\n"
    "out vec2 UV;															\n"
    "																		\n"
    "void main(){															\n"
    "// Output position of the vertex, in clip space : MVP * position		\n"
    "gl_Position = vec4(vertexPosition_modelspace,1);						\n"
    "																		\n"
    "// UV of the vertex. No special space for this one.					\n"
    "UV = vertexUV; 														\n"
    "}																		\n";



static const char backgroundFragmentProgram[] =
    "#version 330 core														\n"
    "																		\n"
    "// Interpolated values from the vertex shaders							\n"
    "in vec2 UV;															\n"
    "																		\n"
    "// Ouput data															\n"
    "out vec3 color;														\n"
    "																		\n"
    "// Values that stay constant for the whole mesh.						\n"
    "uniform sampler2D myTextureSampler;									\n"
    "																		\n"
    "void main(){															\n"
    "// Output color = color of the texture at the specified UV				\n"
    "color = texture2D( myTextureSampler, UV ).rgb;							\n"
    "}																		\n";
    
    

static const char fragmentProgram[] =
    "#version 330 core														\n"
    "																		\n"
    "// Interpolated values from the vertex shaders							\n"
    "in vec3 fragmentColor;													\n"
    "																		\n"
    "// Ouput data															\n"
    "out vec3 color;														\n"
    "																		\n"
    "void main(){															\n"
    "// Output color = color specified in the vertex shader,				\n"
    "// interpolated between all 3 surrounding vertices						\n"
    "color = fragmentColor;													\n"
    "}																		\n";
    
static const char fragmentShader[] =    
"precision lowp float; 														\n"
"varying vec3 vv3colour; 													\n"
"																			\n"
"void main() 																\n"
"{																			\n"
"	gl_FragColor = vec4(vv3colour, 1.0);									\n"
"}																			\n";


static const char vertexShader[] = 
"attribute vec4 av4position;												\n"
"attribute vec3 av3colour;												    \n"
"																			\n"
"uniform mat4 mvp;															\n"
"varying vec3 vv3colour;													\n"
"																			\n"
"void main() 																\n"
"{																			\n"
"	vv3colour = av3colour;													\n"
"	gl_Position = mvp * av4position;										\n"
"}																			\n";				



static const char vertexProgram[] =
    "#version 330 core														\n"
    "																		\n"
    "// Input vertex data, different for all executions of this shader.		\n"
    "layout(location = 0) in vec3 vertexPosition_modelspace;				\n"
    "layout(location = 1) in vec3 vertexColor;								\n"
    "																		\n"
    "// Output data ; will be interpolated for each fragment.				\n"
    "out vec3 fragmentColor;												\n"
    "// Values that stay constant for the whole mesh.						\n"
    "uniform mat4 MVP;														\n"
    "																		\n"
    "void main(){															\n"
    "																		\n"
    "	// Output position of the vertex, in clip space : MVP * position	\n"
    "	gl_Position =  MVP * vec4(vertexPosition_modelspace,1);				\n"
    "																		\n"
    "	// The color of each vertex will be interpolated					\n"
    "	// to produce the color of each fragment							\n"
    "	fragmentColor = vertexColor;										\n"
    "}																		\n";



static const GLfloat gTriangleVertices[] = { 
	 0.0f,  0.5f, 
	-0.5f, -0.5f,
     0.5f, -0.5f 
};

static const GLfloat  squareVertices[]  = {
    -1.0f, -1.0f,
     1.0f, -1.0f,
    -1.0f,  1.0f,
     1.0f,  1.0f,
};

static const GLfloat  textureVertices[] = {
     0.0f, 1.0f,
     1.0f, 1.0f,
     0.0f, 0.0f,
     1.0f, 0.0f,
};



const float g_vertex_buffer_data[] =
{
    /* Front face. */
    /* Bottom left */
    -0.5,  0.5, -0.5,
    0.5, -0.5, -0.5,
    -0.5, -0.5, -0.5,
    /* Top right */
    -0.5,  0.5, -0.5,
    0.5,  0.5, -0.5,
    0.5, -0.5, -0.5,
    /* Left face */
    /* Bottom left */
    -0.5,  0.5,  0.5,
    -0.5, -0.5, -0.5,
    -0.5, -0.5,  0.5,
    /* Top right */
    -0.5,  0.5,  0.5,
    -0.5,  0.5, -0.5,
    -0.5, -0.5, -0.5,
    /* Top face */
    /* Bottom left */
    -0.5,  0.5,  0.5,
    0.5,  0.5, -0.5,
    -0.5,  0.5, -0.5,
    /* Top right */
    -0.5,  0.5,  0.5,
    0.5,  0.5,  0.5,
    0.5,  0.5, -0.5,
    /* Right face */
    /* Bottom left */
    0.5,  0.5, -0.5,
    0.5, -0.5,  0.5,
    0.5, -0.5, -0.5,
    /* Top right */
    0.5,  0.5, -0.5,
    0.5,  0.5,  0.5,
    0.5, -0.5,  0.5,
    /* Back face */
    /* Bottom left */
    0.5,  0.5,  0.5,
    -0.5, -0.5,  0.5,
    0.5, -0.5,  0.5,
    /* Top right */
    0.5,  0.5,  0.5,
    -0.5,  0.5,  0.5,
    -0.5, -0.5,  0.5,
    /* Bottom face */
    /* Bottom left */
    -0.5, -0.5, -0.5,
    0.5, -0.5,  0.5,
    -0.5, -0.5,  0.5,
    /* Top right */
    -0.5, -0.5, -0.5,
    0.5, -0.5, -0.5,
    0.5, -0.5,  0.5,
};

const float g_color_buffer_data[] =
{
    /* Front face */
    /* Bottom left */
    1.0, 0.0, 0.0, /* red */
    0.0, 0.0, 1.0, /* blue */
    0.0, 1.0, 0.0, /* green */
    /* Top right */
    1.0, 0.0, 0.0, /* red */
    1.0, 1.0, 0.0, /* yellow */
    0.0, 0.0, 1.0, /* blue */
    /* Left face */
    /* Bottom left */
    1.0, 1.0, 1.0, /* white */
    0.0, 1.0, 0.0, /* green */
    0.0, 1.0, 1.0, /* cyan */
    /* Top right */
    1.0, 1.0, 1.0, /* white */
    1.0, 0.0, 0.0, /* red */
    0.0, 1.0, 0.0, /* green */
    /* Top face */
    /* Bottom left */
    1.0, 1.0, 1.0, /* white */
    1.0, 1.0, 0.0, /* yellow */
    1.0, 0.0, 0.0, /* red */
    /* Top right */
    1.0, 1.0, 1.0, /* white */
    0.0, 0.0, 0.0, /* black */
    1.0, 1.0, 0.0, /* yellow */
    /* Right face */
    /* Bottom left */
    1.0, 1.0, 0.0, /* yellow */
    1.0, 0.0, 1.0, /* magenta */
    0.0, 0.0, 1.0, /* blue */
    /* Top right */
    1.0, 1.0, 0.0, /* yellow */
    0.0, 0.0, 0.0, /* black */
    1.0, 0.0, 1.0, /* magenta */
    /* Back face */
    /* Bottom left */
    0.0, 0.0, 0.0, /* black */
    0.0, 1.0, 1.0, /* cyan */
    1.0, 0.0, 1.0, /* magenta */
    /* Top right */
    0.0, 0.0, 0.0, /* black */
    1.0, 1.0, 1.0, /* white */
    0.0, 1.0, 1.0, /* cyan */
    /* Bottom face */
    /* Bottom left */
    0.0, 1.0, 0.0, /* green */
    1.0, 0.0, 1.0, /* magenta */
    0.0, 1.0, 1.0, /* cyan */
    /* Top right */
    0.0, 1.0, 0.0, /* green */
    0.0, 0.0, 1.0, /* blue */
    1.0, 0.0, 1.0, /* magenta */
};



GLuint loadShader(GLenum shaderType, const char* pSource) 
{
    GLuint shader = glCreateShader(shaderType);
    if (shader) 
    {
        glShaderSource(shader, 1, &pSource, NULL);
        glCompileShader(shader);
        GLint compiled = 0;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
        if (!compiled) 
        {
            GLint infoLen = 0;
            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
            if (infoLen) 
            {
                char* buf = (char*) malloc(infoLen);
                if (buf) 
                {
                    glGetShaderInfoLog(shader, infoLen, NULL, buf);
                    LOGE("Could not compile shader %d:\n%s\n", shaderType, buf);
                    free(buf);
                }
                glDeleteShader(shader);
                shader = 0;
            }
        }
    }
    return shader;
}



GLuint createProgram(const char* pVertexSource, const char* pFragmentSource) 
{
    GLuint vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource);
    if (!vertexShader)
        return 0;

    GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource);
    if (!pixelShader)
        return 0;

    GLuint program = glCreateProgram();
    if (program) 
    {
        glAttachShader(program, vertexShader);
        checkGlError("glAttachShader");
        glAttachShader(program, pixelShader);
        checkGlError("glAttachShader");
        glLinkProgram(program);
        GLint linkStatus = GL_FALSE;
        glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
        if (linkStatus != GL_TRUE) 
        {
            GLint bufLength = 0;
            glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
            if (bufLength) 
            {
                char* buf = (char*) malloc(bufLength);
                if (buf) 
                {
                    glGetProgramInfoLog(program, bufLength, NULL, buf);
                    LOGE("Could not link program:\n%s\n", buf);
                    free(buf);
                }
            }
            glDeleteProgram(program);
            program = 0;
        }
    }
    return program;
}



GLuint gProgram;
GLuint gvPositionHandle;
GLuint gvTextureHandle;
GLuint gvSamplerHandle;
GLuint _textureGLID;
GLuint gCubeProgram;
GLuint _MatrixID;

GLint iLocPosition = 0;
GLint iLocColour, iLocTexCoord, iLocNormal, iLocMVP;

bool setupGraphics(int w, int h) 
{
    printGLString("Version", GL_VERSION);
    printGLString("Vendor", GL_VENDOR);
    printGLString("Renderer", GL_RENDERER);
    printGLString("Extensions", GL_EXTENSIONS);

    LOGI("setupGraphics(%d, %d)", w, h);
    gProgram = createProgram(strVShader,strFShader);
    if (!gProgram) 
    {
        LOGE("Could not create program.");
        return false;
    }
    gvPositionHandle = glGetAttribLocation(gProgram, "vPosition");
	checkGlError("glGetAttribLocation");
    LOGI("glGetAttribLocation(\"vPosition\") = %d\n", gvPositionHandle);
	gvTextureHandle = glGetAttribLocation(gProgram, "vUV");
    checkGlError("glGetAttribLocation");
    LOGI("glGetAttribLocation(\"vUV\") = %d\n", gvTextureHandle);

	gvSamplerHandle = glGetUniformLocation(gProgram, "myTextureSampler");
	
	gCubeProgram = createProgram( vertexShader, fragmentShader ); 
    iLocPosition = glGetAttribLocation(gCubeProgram, "av4position");
    iLocColour = glGetAttribLocation(gCubeProgram, "av3colour");
    iLocMVP = glGetUniformLocation(gCubeProgram, "mvp");

    glViewport(0, 0, w, h);
    checkGlError("glViewport");
    
    return true;
}



void renderFrame() 
{
    static float grey;
    grey += 0.01f;
    if (grey > 1.0f) {
        grey = 0.0f;
    }
    glClearColor(grey, grey, grey, 1.0f);
    checkGlError("glClearColor");
    glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    checkGlError("glClear");

    glUseProgram(gProgram);
    checkGlError("glUseProgram");

    glVertexAttribPointer(gvPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, squareVertices);
	glVertexAttribPointer(gvTextureHandle, 2, GL_FLOAT, GL_FALSE, 0, textureVertices);
	glUniform1i(gvSamplerHandle, 0);

	glDisable( GL_DEPTH_TEST );
    glActiveTexture( GL_TEXTURE0 );
    glBindTexture( GL_TEXTURE_2D, _textureGLID );
	
    checkGlError("glVertexAttribPointer");
    glEnableVertexAttribArray(gvPositionHandle);
	glEnableVertexAttribArray(gvTextureHandle);
    checkGlError("glEnableVertexAttribArray");
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    checkGlError("glDrawArrays");
    
    glBindTexture( GL_TEXTURE_2D, 0 );
    glUseProgram( 0 );
    
    glUseProgram(gCubeProgram);
 	glEnableVertexAttribArray(iLocPosition);
    glEnableVertexAttribArray(iLocColour);

    /* Populate attributes for position, colour and texture coordinates etc. */
    glVertexAttribPointer(iLocPosition, 3, GL_FLOAT, GL_FALSE, 0, g_vertex_buffer_data);
    glVertexAttribPointer(iLocColour, 3, GL_FLOAT, GL_FALSE, 0, g_color_buffer_data);

    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    
    float mvph[] = {1.280330, -0.985598, -0.578506, -0.577350, 0.000000, 1.971197, -0.578506, -0.577350, -1.280330, -0.985598, -0.578506, -0.577350, 0.000000, 0.000000, 86.575714, 86.602539};
    glUniformMatrix4fv(iLocMVP, 1, GL_FALSE, mvph);

    glDrawArrays(GL_TRIANGLES, 0, 36);
    
    glUseProgram( 0 );
}

extern "C" 
{
    JNIEXPORT int JNICALL Java_com_android_gl2jni_GL2JNILib_init(JNIEnv * env, jobject obj,  jint width, jint height);
    JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_step(JNIEnv * env, jobject obj);
	JNIEXPORT int JNICALL Java_com_android_gl2jni_GL2JNILib_initCAM(JNIEnv * env, jobject obj);
	JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_releaseCAM(JNIEnv * env, jobject obj);
	JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_process(JNIEnv * env, jobject obj);
	JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_capture(JNIEnv * env, jobject obj);
	// JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_loadBitmap(JNIEnv * env, jobject obj, jlong bitmapAddr);
};

////////////////////////////////////////////
cv::VideoCapture *cap;
int _backgroundImageHeight, _backgroundImageWidth;
////////////////////////////////////////////

void setBackgroundImage( const char* image, int w, int h );

JNIEXPORT int JNICALL Java_com_android_gl2jni_GL2JNILib_init(JNIEnv* env, jobject obj, jint width, jint height)
{
    setupGraphics(width, height);
	glGenTextures( 1, &_textureGLID );
}



JNIEXPORT int JNICALL Java_com_android_gl2jni_GL2JNILib_initCAM(JNIEnv* env, jobject obj)
{	
	cap = new cv::VideoCapture();
	cap->set(CV_CAP_PROP_CONVERT_RGB, 1);
	cap->open(0);
	if(cap->isOpened())
		return 1;
	return 0;
}



JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_releaseCAM(JNIEnv* env, jobject obj)
{
	cap->release();
	delete cap;
}



JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_step(JNIEnv* env, jobject obj)
{
    renderFrame();
}



JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_process(JNIEnv* env, jobject obj)
{
}



JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_capture(JNIEnv* env, jobject obj)
{
	cv::Mat frame;
    cap->read(frame);
    cvtColor(frame, frame, CV_BGR2RGB); // TODO: tentar tirar
	setBackgroundImage((const char*)frame.data, frame.cols, frame.rows);	
}



void setBackgroundImage( const char* image, int w, int h )
{
    _backgroundImageHeight = h;
    _backgroundImageWidth  = w;
	
    // "Bind" the newly created texture : all future texture functions will modify this texture
    glBindTexture( GL_TEXTURE_2D, _textureGLID );
    
    // Print the texture size
    //LOGI("BackgroundTexture(w,h)(%d, %d)", _backgroundImageWidth, _backgroundImageHeight);

    // Give the image to OpenGL
    glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, _backgroundImageWidth, _backgroundImageHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image );

    // Poor filtering, or ...
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );

    // ... nice trilinear filtering.
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
}

//JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_loadBitmap(JNIEnv * env, jobject obj, jlong bitmapAddr)
//{
//;
//}

